/*
 * VisualEntity.java
 *
 * Created on 4 December 2007
 *
 * Copyright 2009 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package us.desertlabs.visuals;

import java.awt.Shape;
import java.beans.PropertyChangeEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import us.desertlabs.beans.MappedPropertyChangeEvent;
import us.desertlabs.beans.PropertyChangeObject;

/**
 * This is the base class for all of the entities in the Desert Labs visuals framework.
 * It fires {@link PropertyChangeEvent}s when changes are made to any of the properties.
 *
 * @author Brandon Franklin
 */
public class VisualEntity extends PropertyChangeObject {

    /** The name-to-{@link ImmutablePoint2D} {@link Map} of points of interest */
    private final Map<String,ImmutablePoint2D> pointsOfInterest;

    /** The rotation of the entity, expressed as radians */
    private double rotation;

    /** The scale of the entity, expressed as a size mulitplier */
    private double scale;

    /** The fundamental shape of the modeled entity */
    private Shape shape;

    /** The X and Y distances by which the entity is translated */
    private ImmutablePoint2D translation;

    /**
     * Creates a new instance of {@link VisualEntity} and prepares it for use.
     */
    public VisualEntity() {
        pointsOfInterest = Collections.synchronizedMap( new HashMap<String,ImmutablePoint2D>() );
        rotation = 0;
        scale = 1;
        translation = new ImmutablePoint2D(0,0);
    }

    /**
     * Adds a new Point of Interest to the entity.  Points of Interest are locations in the relative
     * coordinate space of a {@link VisualEntity} to which other Points of Interest can be
     * objects can be attached.
     *
     * @param name the name of the new Point of Interest
     * @param location the location of the Point of Interest in the owning object's relative coordinate space
     */
    public void addPointOfInterest(final String name, final ImmutablePoint2D location) {
        if( pointsOfInterest.containsKey(name) ) {
            throw new IllegalArgumentException("Point of Interest already exists: "+name);
        }

        final MappedPropertyChangeEvent event = new MappedPropertyChangeEvent(this,"pointsOfInterest", pointsOfInterest.get(name), location, name);
        pointsOfInterest.put(name, location);

        getPropertyChangeSupport().firePropertyChange(event);
    }

    /**
     * Returns the Point of Interest on this entity with the specified name.
     *
     * @param name the name of the Point of Interest to retrieve
     *
     * @return the Point of Interest on this entity with the specified name
     *
     * @throws {@link IllegalArgumentException} if the specified Point of Interest does not exist on this entity
     */
    public ImmutablePoint2D getPointOfInterest(final String name) {
        if( !pointsOfInterest.containsKey( name ) ) {
            throw new IllegalArgumentException("Entity does not contain Point of Interest: "+name);
        }
        return pointsOfInterest.get( name );
    }

    /**
     * Returns an unmodifiable {@link Set} of all Point of Interest names that currently exist on this
     * entity.
     *
     * @return an unmodifiable {@link Set} of all Point of Interest names that currently exist on this
     * entity
     */
    public Set<String> getPointOfInterestNames() {
        return Collections.unmodifiableSet( pointsOfInterest.keySet() );
    }

    /**
     * Returns the amount that the entity is rotated by, expressed as radians.
     *
     * @return the amount that the entity is rotated by, expressed as radians
     */
    public double getRotation() {
        return rotation;
    }

    /**
     * Returns the amount to scale the entity by, expressed as a size multiplier.
     *
     * @return the amount to scale the entity by, expressed as a size multiplier
     */
    public double getScale() {
        return scale;
    }

    /**
     * Returns the shape of the entity.
     *
     * @return the shape of the entity
     */
    public Shape getShape() {
        return shape;
    }

    /**
     * Returns the X and Y distances that the entity is translated.
     *
     * @return the X and Y distances that the entity is translated
     */
    public ImmutablePoint2D getTranslation() {
        return translation;
    }

    /**
     * Removes the Point of Interest with the specified name from this entity.
     *
     * @param name the name of the Point of Interest to remove
     *
     * @throws {@link IllegalArgumentException} if the specified Point of Interest does not exist in the entity
     */
    public void removePointOfInterest(final String name) {
        final ImmutablePoint2D removal = pointsOfInterest.remove( name );
        if( removal == null ) {
            throw new IllegalArgumentException("Point of Interest does not exist in entity: "+name);
        }
        final MappedPropertyChangeEvent event = new MappedPropertyChangeEvent(this,"pointsOfInterest", removal, pointsOfInterest.get( name ), name);
        getPropertyChangeSupport().firePropertyChange( event );
    }

    /**
     * Sets the amount to rotate the entity by, expressed as radians.
     *
     * @param theta the amount to rotate the entity by, expressed as radians
     */
    public void setRotation(final double theta) {
        final PropertyChangeEvent event = new PropertyChangeEvent(this,"rotation", rotation, theta);
        rotation = theta;
        getPropertyChangeSupport().firePropertyChange( event );
    }

    /**
     * Sets the amount to scale the entity by, expressed as a size multiplier.
     *
     * @param scale the amount to scale the entity by, expressed as a size multiplier
     */
    public void setScale(final double scale) {
        final PropertyChangeEvent event = new PropertyChangeEvent(this,"scale", this.scale, scale);
        this.scale = scale;
        getPropertyChangeSupport().firePropertyChange( event );
    }

    /**
     * Sets the shape of the of the entity.
     *
     * @param shape the shape of the entity
     *
     * @throws {@link IllegalArgumentException} if the provided {@link Shape} is null
     */
    public void setShape(final Shape shape) {
        if(shape == null) {
            throw new IllegalArgumentException("Shape cannot be null.");
        }

        final PropertyChangeEvent event = new PropertyChangeEvent(this,"shape",this.shape,shape);
        this.shape = shape;
        getPropertyChangeSupport().firePropertyChange(event);
    }

    /**
     * Sets the X and Y distances to translate the entity.
     *
     * @param translation the X and Y distances to translate the entity
     */
    public void setTranslation(final ImmutablePoint2D translation) {
        final PropertyChangeEvent event = new PropertyChangeEvent(this,"translation", this.translation, translation);
        this.translation = translation;
        getPropertyChangeSupport().firePropertyChange( event );
    }

}
